package com.geekgo.flowcontroller;

import com.geekgo.flowcontroller.annotation.DPSRate;
import com.geekgo.flowcontroller.annotation.QPSRate;
import com.geekgo.flowcontroller.annotation.RateLimit;
import com.geekgo.flowcontroller.common.DPSRateBean;
import com.geekgo.flowcontroller.common.QPSRateBean;
import com.geekgo.flowcontroller.common.RateLimitBean;
import com.geekgo.flowcontroller.core.LimitHolderDescriptor;
import com.geekgo.flowcontroller.core.LimiterHolder;
import com.geekgo.flowcontroller.core.acquire.DPSLimitAcquirable;
import com.geekgo.flowcontroller.core.acquire.IRLimitAcquirable;
import com.geekgo.flowcontroller.core.acquire.QPSLimitAcquirable;
import com.geekgo.flowcontroller.core.converter.ArrayLengthRateConverter;
import com.geekgo.flowcontroller.core.converter.NumberRateConverter;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author HeHaoyang
 * @version 2016/8/3 16:36
 */
public class FlowControllerContext {

    private static final Logger LOGGER = LogManager.getLogger(FlowControllerContext.class);
    private Map<String,LimiterHolder> limitKeyMapRLimiter = new HashMap<>(16);
    private Multimap<String,IRLimitAcquirable> acquirableMap = HashMultimap.create();

    public void addRateLimiters(Class<?> clazz){
        RateLimit limit = AnnotationUtils.findAnnotation(clazz,RateLimit.class);
        if (limit!=null){
            Method[] methods = clazz.getDeclaredMethods();
            if (methods!=null&&methods.length>0){
                RateLimitBean limitBean = new RateLimitBean(limit,clazz);

                RateLimitBean methodParentBean = limitBean.cloneOne();
                for(Method method:methods){
                    searchMethod(methodParentBean,method);
                }
            }
        }
    }
    public void searchMethod(RateLimitBean methodParentBean,Method method){
        QPSRate qpsRate = AnnotationUtils.findAnnotation(method,QPSRate.class);
        String methodKey = method.toGenericString();
        methodParentBean.setKey(methodKey);
        QPSLimitAcquirable qpsLimitAcquirable = null;
        QPSRateBean methodRateBean = null;
        if(qpsRate!=null){
            methodRateBean = new QPSRateBean(qpsRate,methodParentBean);
            LimitHolderDescriptor descriptor = new LimitHolderDescriptor(methodRateBean,methodKey);

            LimiterHolder holderByKey = getHolderByKey(descriptor.getLimitKey());
            if (holderByKey!=null){
                holderByKey.addDescriptor(descriptor);
            }else {
                holderByKey = LimiterHolder.createNew(descriptor);
                saveHolder(descriptor.getLimitKey(),holderByKey);
            }
            qpsLimitAcquirable = new QPSLimitAcquirable(holderByKey.getRateLimiter(),methodRateBean.getAcquire());
            saveAcquirable(methodKey,qpsLimitAcquirable);
        }
        RateLimitBean parameterParentBean = methodRateBean==null?methodParentBean:methodRateBean;
        searchParameters(method,methodKey,parameterParentBean);
    }

    public void searchParameters(Method method,String methodKey,RateLimitBean parameterParentBean){
        Class<?>[] parameterTypes = method.getParameterTypes();
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if(parameterAnnotations.length>0){
            int parameterIndex = 0;
            for(Annotation[] annotations:parameterAnnotations){
                if(annotations.length>0){
                    for(Annotation annotation:annotations){
                        if(annotation instanceof DPSRate){
                            DPSRate dpsRate = (DPSRate)annotation;
                            Class<?> parameterType = parameterTypes[parameterIndex];
                            DPSRateBean parameterRateBean = null;
                            if(parameterType.isArray()){
                                parameterRateBean = new DPSRateBean(dpsRate,new ArrayLengthRateConverter(parameterIndex),parameterParentBean);
                            }else {

                                try {
                                    ConvertUtils.convert(parameterTypes[parameterIndex],int.class);
                                    parameterRateBean = new DPSRateBean(dpsRate,new NumberRateConverter(parameterIndex),parameterParentBean);
                                } catch (Exception e) {
                                    LOGGER.error("convert error", e);
                                    throw new IllegalArgumentException(
                                            "@DPSRate can only be marked with Numbers or Array, but actually is:[" + methodKey + "->" + parameterIndex + ":" + parameterType + "]");
                                }
                            }
                            LimitHolderDescriptor dpsDescriptor = new LimitHolderDescriptor(parameterRateBean,methodKey);
                            LimiterHolder holderByKey = getHolderByKey(dpsDescriptor.getLimitKey());
                            if(holderByKey!=null){
                                holderByKey.addDescriptor(dpsDescriptor);
                            }else{
                                holderByKey = LimiterHolder.createNew(dpsDescriptor);
                            }
                            DPSLimitAcquirable dpsLimitAcquirable = new DPSLimitAcquirable(holderByKey.getRateLimiter(),parameterRateBean.getConverter());
                            saveAcquirable(methodKey,dpsLimitAcquirable);
                        }
                    }
                }
                parameterIndex++;
            }
        }
    }

    public Collection<IRLimitAcquirable> getAcquirableByKey(String methodKey){
        return this.acquirableMap.get(methodKey);
    }

    public LimiterHolder getHolderByKey(String key){
        return this.limitKeyMapRLimiter.get(key);
    }
    public void saveHolder(String key,LimiterHolder limiterHolder){
        this.limitKeyMapRLimiter.put(key,limiterHolder);
    }
    public void saveAcquirable(String methodKey,IRLimitAcquirable limitAcquirable){
        this.acquirableMap.put(methodKey,limitAcquirable);
    }
}
